<?php
    //1.定义命名空间与本文件所在目录层级相同（houdunwang\model）
    //2.因为composer自动载入类的需要
    //3.命名空间namespace之前不能有输出！
    namespace houdunwang\model;
    //1.引入全局的PDO异常类
    //2.因为PDOException也是一个类
    use PDOException;
    //1.引入全局的PDO类
    //2.因为PDO也是一个类
    use PDO;

    //1.定义基类用于Entry类中的arc方法调用其中的方法
    //2.进行查询出相应的数据
    class Base{
        //1.定义私有属性$pdo
        //2.用于存储生成好的pdo对象以供类内部的各处调用
        //3.定义为静态属性可在多次实例化Base时调用
        private static $pdo = null;
        //1.定义私有属性$config
        //2.用于存储database.php传递过来的数据库配置信息
        //链接数据库用
        private $config;
        //1.定义私有属性$table
        //2.用于查询表时需要的表名
        private $table;
        //1.定义私有属性$where
        //2.用于存储Entry类中的arc方法中传递过来的查询条件
        private $where = "";

        //1.定义构造方法执行链接数据库方法
        //2.存储表名及数据库配置信息
        public function __construct($config,$table){
            //存储表名
            $this->table = $table;
            //存储数据库配置信息
            $this->config = $config;
            //链接数据库
            $this->connect();
        }

        //1.定义连接数据库的函数
        //2.若在try之中产生了"PDO异常错误"则会被catch捕捉到并可
        //自定义错误的显示样式
        private function connect(){
            //1.如果$pdo已经存在则返回
            //2.不再执行以下链接数据库的操作

            /*if(!is_null(self::$pdo)){//链式调用只算一次（）
                echo "已经链接过了";
                return false;
            }*///或
            if(self::$pdo){//链式调用只算一次（重新实例化的时候才会执行构造方法）
                //echo "已经链接过了";
                return false;
            }

            try{
                //1.设置主机地址、选择数据库、数据库的用户名及密码
                //2.mysql:host=...;dbname=...是dbname而不是db_name;
                $dsn = "mysql:host=".$this->config["db_host"].";dbname=".$this->config["db_name"];
                $user = $this->config["db_user"];
                $pwd = $this->config["db_pwd"];
                //创建PDO对象、连接数据库
                $pdo = new PDO($dsn,$user,$pwd);
                //1.设置错误的处理方式
                //2.默认为ERRMODE_SILENT即不显示错误
                //3.在这里设置成“抛出异常”能够显示异常的错误，供catch捕捉到
                $pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
                //1.设置字符编码集
                //2.浏览器默认的字符编码为UTF8，从数据库调取出及写入数据
                //需要与客户端的字符集格式一致，否则会出现显示乱码及写入数据出错的情况
                $pdo->exec("SET NAMES {$this->config['db_charset']}");
                //1.将设置好的PDO对象存到仅有属性$pdo中
                //2.供下面各方法的调用
                self::$pdo = $pdo;
            }catch (PDOException $e){
                //1.捕捉异常错误
                //2.捕捉后可根据开发人员自身的需要对其处理
                exit($e->getMessage());
            }
        }

        //1.查询数据库操作
        //2.有结果集返回
        //3.可根据开发人员自已输入的sql语句查询相应的数据
        //($sql为传入的sql语句)
        public function q($sql){
            try{
                //1.查询数据库
                //2.返回的结果集是一个对象格式
                $result = self::$pdo->query($sql);
                //1.从结果集对象获得数组（默认既有索引又有关联键名）
                //2.PDO::FETCH_ASSOC 获得关联数组
                //关联数组方便数组的调用访问
                $data = $result->fetchAll(PDO::FETCH_ASSOC);
                //将数据返回供自身处理
                return $data;
            }catch (PDOException $e){
                exit($e->getMessage());
            }
        }

        //1.定义查询条件的函数
        //2.将用户传递过来的查询条件存储在私有属性$where中
        public function where($where){
            $this->where = "WHERE ".$where;//要有空格"WHERE "
            //返回当前对象以供链式调用
            return $this;
        }

        //1.定义获取数据组相应限制条件下数据的函数
        //2.直接调用q()查询数据、$this->where为相应的修饰条件
        //3.也可以将q()与get()合并为一个（如果只用SELECT * FROM...的话）
        //get()只能输出全部的字段，q()可以自定义字段输出
        public function get($sort=""){

            //1.获取表的主键
            //2.用于排序时需要的字段
            $priKey = $this->getPri($this->table);

            //1.定义排序的规则
            //2.如果用户传入了get()的参数（可选有asc升序/desc降序/rand()随机排序）
            //则按用户传入的值进行排序，否则默认不进行排序
            switch ($sort){
                case "asc"://升序
                    $order = "ORDER BY {$priKey} asc";
                    break;
                case "desc"://降序
                    $order = "ORDER BY {$priKey} desc";
                    break;
                case "rand()"://随机排序
                    $order = "ORDER BY rand()";
                    break;
                default://默认不进行任何排序
                    $order = "";
            }

            //1.输出$this->where条件下数据（加上以上的排序规则语句）
            //2.表名$this->table在Model类中已经赋值（调用该方法的小写类名）
            $data = $this->q("SELECT * FROM {$this->table} {$this->where} {$order}");
            //返回数据供Entry类的arc的输出
            return $data;
        }

        //1.定义获取两张表（相互关联）的数据的方法
        //2.例如本项目中的student表与grade表通过gid来关联
        //从而调用student数据时能够获取到学生所对应班级名称
        public function getJoinTable($table1,$table2){
            //获取表1、表2的主键：
            //例如select * from student join grade on student.gid = grade.gid where...
            //表1的主键用于排序使用，表2的主键为关联字段
            $priKey1 = $this->getPri("$table1");
            $priKey2 = $this->getPri("$table2");

            //1.定义关联的sql语句（并进行排序查询）
            //2.执行有结果集返回的q方法输出查询到的数据
            $sql = "SELECT * FROM $table1 as table1 join $table2 as table2 on table1.$priKey2 = table2.$priKey2 {$this->where} ORDER BY table1.$priKey1 asc";
            return $this->q($sql);
        }



        //1.定义统计指定字段的数量的函数
        //2.根据用户传入的字段及执行的where方法
        //统计对应字段在where限制条件的数量
        public function count($field="*"){
            //查询$this->where条件下$this->table表中的$field字段的数量
            $data = $this->q("SELECT count({$field}) as count FROM {$this->table} {$this->where}");
            //返回查询到的字段的数量
            return $data[0]["count"];
        }

        //1.获取查询的表的字段的主键的函数
        //2.查询表结构并遍历其中的Key键值对
        //（"Key"=>"PRI"代表字段为主键）
        public function getPri($table){
            //1.查询出表的结构信息
            //2.用于找到其中的主键
            $data = $this->q("DESC $table");
            //1.遍历其中的键名为Key的数据
            //2.如果值为PRI则将其返回供find函数的使用
            //（查询出指定主键的数据）
            foreach ($data as $v){
                if($v["Key"]=="PRI"){
                    //返回出主键停止遍历
                    return $v["Field"];
                }
            }
        }

        //1.定义查询出指定主键的数据
        //2.查询主键为传入的参数值
        //3.得到的结果为二维数组将其转为一维
        public function find($pri){
            //调用获取表的主键的函数
            $priKey  = $this->getPri($this->table);
            //定义指定主键的条件
            $this->where("{$priKey}={$pri}");
            //查询出指定主键的数据
            $data = $this->q("SELECT * FROM {$this->table} {$this->where}");
            //将查询出的数据赋值到当前对象的属性供toArray()返回
            $this->findData = current($data);
            //返回当前对象供链式调用（调用toArray()方法）
            return $this;
        }

        //1.find()函数之后的返回查询到的主键的数据
        //2.在find()方法后链式调用该方法
        public function toArray(){
            return $this->findData;
        }

        //1.调用find()方法及toArray()方法
        //2.一次性查询出指定主键的数据
        public function findToArray($pri){
            return $this->find($pri)->toArray();
        }



        //1.定义无结果集操作的方法e()
        //2.用于执行对数据库的增删改时需要调用
        public function e($sql){
            //1.返回的结果将是受作用的条数
            //2.可能作为显示有多少被操作
            return self::$pdo->exec($sql);
        }

        //1.定义插入数据的方法
        //2.调用上述的e方法，接收传入的$data数据新增一条数据
        //3.返回当前对象供以后可能的链式调用
        public function insert($newData){
            $this->e("INSERT INTO {$this->table} SET {$newData}");
            return $this;
        }

        //1.定义删除数据的方法
        //2.接收传递进来的主键值进行删除该主键值的数据
        public function delete($pri){
            //调用获取主键名的方法getPri()
            $priKey = $this->getPri($this->table);
            //调用设置限制条件的方法where()
            $this->where("{$priKey}={$pri}");
            //调用上述的e方法删除所设置的限制条件的数据
            $this->e("DELETE FROM {$this->table} {$this->where}");
            //返回当前对象供以后可能的链式调用
            return $this;
        }

        //1.定义修改数据的方法
        //2.设置新的数据值并执行sql语句修改数据
        public function update($pri,$newData){
            //获取表的主键名
            $priKey = $this->getPri($this->table);
            //设置限制条件（要修改的哪一条数据）
            $this->where("{$priKey}={$pri}");
            //执行sql语句修改表中的数据
            $this->e("UPDATE {$this->table} SET {$newData} {$this->where}");
        }

    }
